package particle;

import java.net.MalformedURLException;
import java.net.URL;

import particle.ParticleNode.ColorData;
import javafx.scene.effect.BlendMode;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;

public class ParticleSystem {

	private int totalNum;

	private boolean isModeA = true;
	private String plistFile;
	private double elapsedTime;
	private ParticleModeA modeAProp;
	private ParticleModeB modeBProp;

	private double emitCounter;
	private double emitRate;
	private int particleIndex;
	private int particleCounter;
	private boolean isActive;
	private boolean isBlendAdditive;
	private boolean isAutoRemove;
	private boolean isVisible;

	private PlistParser parser;
	private Image particleTexture;

	private double duration;
	private ParticleBatchNode nodeMgr;
	private PositionType positionType;

	private BaseProperties baseProp;

	ParticleSystem(String plistFile) {
		this.plistFile = plistFile;
		parser = new PlistParser(plistFile);
		
		baseProp = new BaseProperties(parser);
		totalNum = (int) (get(Properties.TOTAL_PARTICLES));
		duration = get(Properties.EMITTER_LIFE);
		
		nodeMgr = new ParticleBatchNode(totalNum);
		init();
	}

	public void init() {
		try {
			URL u = new URL(parser.getTexture());
			particleTexture = new Image(u.toString());
		} catch (MalformedURLException e) {
			System.out.println("图片纹理信息错误");
		}
		if (particleTexture != null) {
			int counter = -1;
			for (ParticleNode p : nodeMgr.getNodes()) {
				p = new ParticleNode();
				p.viewQuad.setImage(particleTexture);
				p.index = ++counter;
				nodeMgr.addChild(p);
			}
		}
		if (get(Properties.MAX_RADIUS) == 0 && get(Properties.MIN_RADIUS) == 0) {
			isModeA = true;
		} else {
			isModeA = false;
		}

		isActive = true;
		isBlendAdditive = false;
		isAutoRemove = true;
		isVisible = true;

		elapsedTime = 0;
		emitCounter = 0;
		particleIndex = 0;
		particleCounter = 0;

		emitRate = totalNum / baseProp.getParticleLife();
		positionType = PositionType.FreeType;

		// mode A
		if (isModeA) {
			modeAProp = new ParticleModeA();
			modeAProp.gravityX = get(Properties.GRAVITY_X);
			modeAProp.gravityY = get(Properties.GRAVITY_Y);
			modeAProp.radialAccel = get(Properties.RADIAL_ACCEL);
			modeAProp.radialAccelVar = parser
					.getProperty(Properties.RADIAL_ACCEL_VAR);
			modeAProp.speed = get(Properties.START_SPEED);
			modeAProp.speedVar = get(Properties.SPEED_VAR);
			modeAProp.tangentialAccel = parser
					.getProperty(Properties.TAN_ACCEL);
			modeAProp.tangentialAccelVar = parser
					.getProperty(Properties.TAN_ACCEL_VAR);
		} else {
			modeBProp = new ParticleModeB();
			modeBProp.endRadius = get(Properties.MAX_RADIUS);
			modeBProp.endRadiusVar = parser
					.getProperty(Properties.MAX_RADIUS_VAR);
			modeBProp.startRadius = get(Properties.MIN_RADIUS);
			modeBProp.startRadiusVar = 0;
			modeBProp.rotatePerSecond = parser
					.getProperty(Properties.ANGLE_SPEED);
			modeBProp.rotatePerSecondVar = parser
					.getProperty(Properties.ANGLE_SPEED_VAR);
		}
	}

	public void update(double dt) {
		if (isActive && emitRate != 0) {
			double rate = 1.0 / emitRate;
			if (particleCounter < totalNum)
				emitCounter += dt;
			System.out.println("----system test----1:"+emitCounter+","+rate);
			while (particleCounter < totalNum && emitCounter > rate) {
				addParticle();
				emitCounter -= rate;
			}
			elapsedTime += dt;
			if (duration != -1 && duration < elapsedTime) {
				stopSystem();
			}
		}

		particleIndex = 0;

		if (isVisible) {
			System.out.println("----system test----:"+particleIndex+","+particleCounter);			
			while (particleIndex < particleCounter) {
				ParticleNode p = nodeMgr.getNodes()[particleIndex];
				p.timeToLive -= dt;

				if (p.timeToLive > 0) {
					if (isModeA) {
						double tempX = p.posX;
						double tempY = p.posY;
						double radialX = tempX;
						double radialY = tempY;
						double tangentialX = tempX;
						double tangentialY = tempY;

						radialX *= p.aStatus.accelX;
						radialY *= p.aStatus.accelX;

						double newY = tangentialX;
						tangentialX = -tangentialY;
						tangentialY = newY;
						tangentialX *= p.aStatus.accelY;
						tangentialY *= p.aStatus.accelY;

						tempX = radialX + tangentialX + modeAProp.gravityX;
						tempY = radialY + tangentialY + modeAProp.gravityY;
						tempX *= dt;
						tempY *= dt;

						p.aStatus.dirX += tempX;
						p.aStatus.dirY += tempY;

						tempX = p.aStatus.dirX * dt;
						tempY = p.aStatus.dirY * dt;

						p.posX += tempX;
						p.posY += tempY;

					} else {
						p.bStatus.angle += modeBProp.rotatePerSecond * dt;
						p.bStatus.radius += modeBProp.startRadiusVar * dt;

						p.posX = -Math.cos(p.bStatus.angle) * p.bStatus.radius;
						p.posY = -Math.sin(p.bStatus.angle) * p.bStatus.radius;
					}

					double newRed = p.color.getRed() + p.deltaColor.getRed()
							* dt;
					double newGreen = p.color.getGreen()
							+ p.deltaColor.getGreen() * dt;
					double newBlue = p.color.getBlue() + p.deltaColor.getBlue()
							* dt;
					double newAlpha = p.color.getOpacity()
							+ p.deltaColor.getOpacity() * dt;
					p.color = new Color(newRed, newGreen, newBlue, newAlpha);

					p.size += p.deltaSize * dt;
					p.size = Math.max(0, p.size);
					p.rotation += p.deltaRotation * dt;

					if (positionType == PositionType.GroupedType) {
						p.viewQuad.setTranslateX(p.posX);
						p.viewQuad.setTranslateY(p.posY);
					} else {
						double diffX = p.posX - p.startPosX;
						double diffY = p.posY - p.startPosY;
						p.viewQuad.setTranslateX(p.posX - diffX);
						p.viewQuad.setTranslateY(p.posY - diffY);
					}
					p.viewQuad.setRotate(p.rotation);
					p.viewQuad.setScaleX(p.size);
					p.viewQuad.setScaleY(p.size);

					new ColorChanger(p.color,p.viewQuad);
					particleIndex++;
				} else {
					int current = p.index;
					nodeMgr.removeByIndex(current);
					particleCounter--;

					if (particleCounter == 0 && isAutoRemove) {
						stopSystem();
					}
				}
			}
		}
	}

	public void update() {
		this.update(0.0);
	}

	public void updateBlend() {
		if (isBlendAdditive) {
			for (int i = 0; i < particleCounter; i++) {
				nodeMgr.getNodes()[i].viewQuad.setBlendMode(BlendMode.LIGHTEN);
			}
		}
	}

	public void stopSystem() {
		isActive = false;
		elapsedTime = duration;
		emitCounter = 0;
		System.out.println("----system stop----");
	}

	public void resetSystem() {
		isActive = true;
		elapsedTime = 0;
		for (particleIndex = 0; particleIndex < totalNum; particleIndex++) {
			nodeMgr.getNodes()[particleIndex].timeToLive = 0;
		}
		System.out.println("----system start----");
	}

	public boolean isVisible() {
		return isVisible;
	}

	public void setVisible(boolean isVisible) {
		this.isVisible = isVisible;
		nodeMgr.setVisible(isVisible);
	}

	public boolean isFull() {
		return totalNum <= particleCounter;
	}

	public int getTotalNum() {
		return totalNum;
	}

	public void setTotalNum(int totalNum) {
		this.totalNum = totalNum;
	}

	public void setPlistFile(String s) {
		parser = new PlistParser(s);
		this.init();
		update();
	}

	public String getPlistFile() {
		return plistFile;
	}

	private enum PositionType {
		FreeType, GroupedType, RelativeType;
	}

	private void addParticle() {
		ParticleNode p = null;
		if (!isFull()) {
			nodeMgr.getNodes()[particleCounter].viewQuad.setVisible(true);
			particleCounter++;
			p = nodeMgr.getNodes()[particleCounter];
		}

		if (p != null) {
			// time and position
			p.timeToLive = get(Properties.LIFE_SPAN)
					+ get(Properties.LIFE_SPAN_VAR) * random();
			p.timeToLive = Math.max(p.timeToLive, 0);

			p.startPosX = p.posX = get(Properties.SRC_POS_X)
					+ get(Properties.SRC_POS_X_VAR) * random();
			p.startPosY = p.posY = get(Properties.SRC_POS_Y)
					+ get(Properties.SRC_POS_Y_VAR) * random();
			p.viewQuad.setTranslateX(p.startPosX);
			//TODO attemp
			p.viewQuad.setTranslateY(p.startPosY);

			// color
			double tempSR = clamp(get(Properties.SRC_COLOR_RED)
					+ get(Properties.SRC_COLOR_RED_VAR) * random());
			double tempSG = clamp(parser
					.getProperty(Properties.SRC_COLOR_GREEN)
					+ get(Properties.SRC_COLOR_GREEN_VAR) * random());
			double tempSB = clamp(get(Properties.SRC_COLOR_BLUE)
					+ get(Properties.SRC_COLOR_BLUE_VAR) * random());
			double tempSA = clamp(parser
					.getProperty(Properties.SRC_COLOR_ALPHA)
					+ get(Properties.SRC_COLOR_ALPHA_VAR) * random());

			double tempER = ((clamp(parser
					.getProperty(Properties.DEST_COLOR_RED)
					+ get(Properties.DEST_COLOR_RED_VAR) * random())) - tempSR)
					/ p.timeToLive;
			double tempEG = ((clamp(parser
					.getProperty(Properties.DEST_COLOR_GREEN)
					+ get(Properties.DEST_COLOR_GREEN_VAR) * random())) - tempSG)
					/ p.timeToLive;
			double tempEB = ((clamp(parser
					.getProperty(Properties.DEST_COLOR_BLUE)
					+ get(Properties.DEST_COLOR_BLUE_VAR) * random())) - tempSB)
					/ p.timeToLive;
			double tempEA = ((clamp(parser
					.getProperty(Properties.DEST_COLOR_ALPHA)
					+ get(Properties.DEST_COLOR_ALPHA_VAR) * random())) - tempSA)
					/ p.timeToLive;

			p.color = new Color(tempSR, tempSG, tempSB, tempSA);
			p.deltaColor = new ColorData(tempER, tempEG, tempEB, tempEA);

			// size
			p.size = get(Properties.SRC_SIZE) + get(Properties.SRC_SIZE_VAR)
					* random();
			p.size = Math.max(p.size, 0);

			if (get(Properties.DEST_SIZE) == get(Properties.SRC_SIZE))
				p.deltaSize = 0;
			else {
				double endS = get(Properties.DEST_SIZE)
						+ get(Properties.DEST_SIZE_VAR) * random();
				endS = Math.max(endS, 0);
				p.deltaSize = (endS - p.size) / p.timeToLive;
			}

			// rotation
			p.rotation = get(Properties.SRC_ANGLE)
					+ get(Properties.SRC_ANGLE_VAR) * random();
			p.deltaRotation = (get(Properties.DEST_ANGLE)
					+ get(Properties.DEST_ANGLE_VAR) * random() - p.rotation)
					/ p.timeToLive;
			

			// direction
			double tempDir = radians(get(Properties.SHOOT_ANGLE)
					+ get(Properties.SHOOT_ANGLE_VAR) * random());

			if (isModeA) {
				double sp = get(Properties.START_SPEED)
						+ get(Properties.SPEED_VAR) * random();
				p.aStatus.dirX = Math.cos(tempDir) * sp;
				p.aStatus.dirY = Math.sin(tempDir) * sp;

				p.aStatus.accelX = get(Properties.RADIAL_ACCEL)
						+ get(Properties.RADIAL_ACCEL_VAR) * random();
				p.aStatus.accelY = get(Properties.TAN_ACCEL)
						+ get(Properties.TAN_ACCEL_VAR) * random();
			} else {
				p.bStatus.radius = get(Properties.MIN_RADIUS);
				p.bStatus.deltaRadius = ((get(Properties.MAX_RADIUS) + get(Properties.MAX_RADIUS_VAR)
						* random()) - p.bStatus.radius)
						/ p.timeToLive;
				p.bStatus.angle = tempDir;
				p.bStatus.degreesps = clamp(get(Properties.ANGLE_SPEED)
						+ get(Properties.ANGLE_SPEED_VAR) * random());
			}
		}
	}
	
	public ParticleBatchNode getBatchNode() {
		return nodeMgr;
	}

	private double random() {
		return Math.random() * 2 - 1;
	}

	private double clamp(double src) {
		System.out.println(src+":to:"+(src > 1 ? (src - 1) : (src < 0 ? (src + 1) : src)));
		return src > 1 ? (src - 1) : (src < 0 ? (src + 1) : src);
	}

	private double radians(double src) {
		return src > 360 ? (src - 360) : (src < 0 ? (src + 360) : src);
	}

	private double get(int propertyId) {
		return parser.getProperty(propertyId);
	}
}
